Conversation
…chatwoot#13295) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…oot#13278) Fixes chatwoot#13097 ### Problem The PR chatwoot#12176 removed the `before_save :setup_webhooks` callback to fix a race condition where Meta's webhook verification request arrived before the channel was saved to the database. This change broke manual WhatsApp Cloud channel setup. While embedded signup explicitly calls `channel.setup_webhooks` in `EmbeddedSignupService`, manual setup had no equivalent call - meaning the `subscribed_apps` endpoint was never invoked and Meta never sent webhook events to Chatwoot. ### Solution Added an `after_commit` callback that triggers webhook setup for manual WhatsApp Cloud channels
… incoming messages (chatwoot#13319) Fixes chatwoot#13317 Fixes an issue where WhatsApp attachment messages (images, audio, video, documents) were failing to download. Messages were being created but without attachments. The `phone_number_id` parameter was being passed to the `GET /<MEDIA_ID>` endpoint when downloading incoming media. According to Meta's documentation: > "Note that `phone_number_id` is optional. If included, the request will only be processed if the business phone number ID included in the query matches the ID of the business phone number **that the media was uploaded on**." For incoming messages, media is uploaded by the customer, not by the business phone number. Passing the business's `phone_number_id` causes validation to fail with error: `Param phone_number_id is not a valid whatsapp business phone number id ID` This PR removes the `phone_number_id` parameter from the media URL request for incoming messages.
Co-authored-by: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com> Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: aakashb95 <aakashbakhle@gmail.com>
Fixes signatures being generated on top of existing signature in draft for improve, tone change and grammar
…13339) - Fix ordered lists being sent as unordered lists in WhatsApp integrations - The WhatsApp markdown renderer was converting all lists to bullet points (-), ignoring numbered list formatting - Added ordered list support by tracking list_type and list_item_number from CommonMarker AST metadata Before: Input: "1. First\n2. Second\n3. Third" Output: "- First\n- Second\n- Third" After: Input: "1. First\n2. Second\n3. Third" Output: "1. First\n2. Second\n3. Third"
…oot#13343) Added Portuguese (Brazil) (`pt_BR`) to the CSAT template language dropdown
…woot#12956) - Use resolved contacts instead of accounts.contacts for search --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Pranav <pranavrajs@gmail.com>
…hatwoot#13276) ## Linear task: https://linear.app/chatwoot/issue/CW-6318/searchkickimporterror-type-=-mapper-parsing-exception-reason-=-failed ## Description Fixes Elasticsearch `mapper_parsing_exception` errors that occur when `campaign_id` contain non-numeric string values ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? - Unit tests - use a local OpenSearch 3.4.0 cluster to verify actual indexing behavior. ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Removes `campaign_id` from the message search index payload to simplify `additional_attributes`, keeping only `automation_rule_id`. > > - `Messages::SearchDataPresenter#additional_attributes_data` now returns only `automation_rule_id` > - Specs updated to stop asserting `campaign_id` and continue validating `automation_rule_id` and email subject handling > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5a9c8eb. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
- Improved design for the Chatwoot sign up page
… load (chatwoot#13355) High-traffic accounts generate excessive database writes due to agents frequently switching between conversations. The update_last_seen endpoint was being called every time an agent loaded a conversation, resulting in unnecessary updates to agent_last_seen_at and assignee_last_seen_at even when there were no new messages to mark as read. #### Solution Implemented throttling for the update_last_seen endpoint: **Unread messages present:** - Updates immediately without throttling to maintain accurate read/unread state - Uses assignee_unread_messages for assignees, unread_messages for other agents **No unread messages:** - Throttles updates to once per hour per conversation - Checks if agent_last_seen_at is older than 1 hour before updating - For assignees, checks both agent_last_seen_at AND assignee_last_seen_at - updates if either timestamp is old - Skips DB write if all relevant timestamps were updated within the last hour - Consolidated two separate update_column calls into a single update_columns call to reduce DB queries
# Pull Request Template ## Description The Help Center sitemap endpoint (`/hc/:portal_slug/sitemap.xml`) previously rendered a `<sitemapindex>` element while embedding article URLs directly, which does not align with the sitemap specification. This change fixes the structure by: - Replacing `<sitemapindex>` with `<urlset>` - Adding the required sitemap XML namespace - Rendering each published article as a `<url>` entry with `<loc>` and `<lastmod>` This ensures the endpoint outputs a valid, self-contained sitemap document. Fixes chatwoot#13334 ## Type of change Please delete options that are not relevant. - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? - Updated the existing `portals_controller_spec.rb` - Adjusted assertions to validate a `<urlset>` root element and the sitemap XML namespace - Verified that the sitemap returns only published article URLs - Ran the updated RSpec controller specs locally ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules
We are expanding Chatwoot’s automation capabilities by introducing **Conversation Workflows**, a dedicated section in settings where teams can configure rules that govern how conversations are closed and what information agents must fill before resolving. This feature helps teams enforce data consistency, collect structured resolution information, and ensure downstream reporting is accurate. Instead of having auto‑resolution buried inside Account Settings, we introduced a new sidebar item: - Auto‑resolve conversations (existing behaviour) - Required attributes on resolution (new) This groups all conversation‑closing logic into a single place. #### Required Attributes on Resolve Admins can now pick which custom conversation attributes must be filled before an agent can resolve a conversation. **How it works** - Admin selects one or more attributes from the list of existing conversation level custom attributes. - These selected attributes become mandatory during resolution. - List all the attributes configured via Required Attributes (Text, Number, Link, Date, List, Checkbox) - When an agent clicks Resolve Conversation: If attributes already have values → the conversation resolves normally. If attributes are missing → a modal appears prompting the agent to fill them. <img width="1554" height="1282" alt="CleanShot 2025-12-10 at 11 42 23@2x" src="https://github.com/user-attachments/assets/4cd5d6e1-abe8-4999-accd-d4a08913b373" /> #### Custom Attributes Integration On the Custom Attributes page, we will surfaced indicators showing how each attribute is being used. Each attribute will show badges such as: - Resolution → used in the required‑on‑resolve workflow - Pre‑chat form → already existing <img width="2390" height="1822" alt="CleanShot 2025-12-10 at 11 43 42@2x" src="https://github.com/user-attachments/assets/b92a6eb7-7f6c-40e6-bf23-6a5310f2d9c5" /> #### Admin Flow - Navigate to Settings → Conversation Workflows. - Under Required attributes on resolve, click Add Required Attribute. - Pick from the dropdown list of conversation attributes. - Save changes. Agents will now be prompted automatically whenever they resolve. <img width="2434" height="872" alt="CleanShot 2025-12-10 at 11 44 42@2x" src="https://github.com/user-attachments/assets/632fc0e5-767c-4a1c-8cf4-ffe3d058d319" /> #### NOTES - The Required Attributes on Resolve modal should only appear when values are missing. - Required attributes must block the resolution action until satisfied. - Bulk‑resolve actions should follow the same rules — any conversation missing attributes cannot be bulk‑resolved, rest will be resolved, show a notification that the resolution cannot be done. - API resolution does not respect the attributes. --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Pranav <pranav@chatwoot.com>
## Summary - Add `has_more` to contacts search API response to enable infinite scroll without expensive count queries - Set `count` to the number of items in the current page instead of total count - Implement "Load more" button for contacts search results - Keep existing contacts visible while loading additional pages ## Changes ### Backend - Add `fetch_contacts_with_has_more` method that fetches N+1 records to determine if more pages exist - Return `has_more` in search endpoint meta response - Set `count` to current page size instead of total count ### Frontend - Add `APPEND_CONTACTS` mutation for appending contacts without clearing existing ones - Update search action to support `append` parameter - Add `ContactsLoadMore` component with loading state - Update `ContactsListLayout` to support infinite scroll mode - Update `ContactsIndex` to use infinite scroll for search view
…13152) Fixes https://linear.app/chatwoot/issue/CW-6358/handling-of-nil-conversation-in-the-readstatusservice Improve handling of nil conversation in the `ReadStatusService` to prevent potential errors. Ensure that the conversation is checked before performing updates to message status. This change fixes the below error. ``` NoMethodError: undefined method 'conversations' for nil (NoMethodError) channel.inbox.contact_inboxes.find_by(source_id: tt_conversation_id).conversations.first ^^^^^^^^^^^^^^ from app/services/tiktok/messaging_helpers.rb:29:in 'Tiktok::MessagingHelpers#find_conversation' from app/services/tiktok/read_status_service.rb:13:in 'Tiktok::ReadStatusService#conversation' from app/services/tiktok/read_status_service.rb:9:in 'Tiktok::ReadStatusService#perform' from app/jobs/webhooks/tiktok_events_job.rb:67:in 'Webhooks::TiktokEventsJob#im_mark_read_msg' from app/jobs/webhooks/tiktok_events_job.rb:31:in 'Webhooks::TiktokEventsJob#process_event' from app/jobs/webhooks/tiktok_events_job.rb:15:in 'block in Webhooks::TiktokEventsJob#perform' from app/jobs/mutex_application_job.rb:23:in 'MutexApplicationJob#with_lock' from app/jobs/webhooks/tiktok_events_job.rb:14:in 'Webhooks::TiktokEventsJob#perform' from activejob (7.1.5.2) lib/active_job/execution.rb:68:in 'block in ActiveJob::Execution#_perform_job' from activesupport (7.1.5.2) lib/active_support/callbacks.rb:121:in 'block in ActiveSupport::Callbacks#run_callbacks' from i18n (1.14.7) lib/i18n.rb:353:in 'I18n::Base#with_locale' from activejob (7.1.5.2) lib/active_job/translation.rb:9:in 'block (2 levels) in <module:Translation>' from activesupport (7.1.5.2) lib/active_support/callbacks.rb:130:in 'BasicObject#instance_exec' from activesupport (7.1.5.2) lib/active_support/callbacks.rb:130:in 'block in ActiveSupport::Callbacks#run_callbacks' from activesupport (7.1.5.2) lib/active_support/core_ext/time/zones.rb:65:in 'Time.use_zone' from activejob (7.1.5.2) lib/active_job/timezones.rb:9:in 'block (2 levels) in <module:Timezones>' from activesupport (7.1.5.2) lib/active_support/callbacks.rb:130:in 'BasicObject#instance_exec' from activesupport (7.1.5.2) lib/active_support/callbacks.rb:130:in 'block in ActiveSupport::Callbacks#run_callbacks' from activesupport (7.1.5.2) lib/active_support/callbacks.rb:141:in 'ActiveSupport::Callbacks#run_callbacks' from activejob (7.1.5.2) lib/active_job/execution.rb:67:in 'ActiveJob::Execution#_perform_job ``` Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
) Fixes https://linear.app/chatwoot/issue/CW-6357/ensure-authentication-when-fetching-attachments Update the attachment download method to include the access token in the request headers, ensuring proper authentication when fetching attachments. https://business-api.tiktok.com/portal/docs?id=1832184455450626 Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
## Linear issue https://linear.app/chatwoot/issue/CW-6289/limit-the-number-of-notifications-per-user-to-300 ## Description Limits the number of notifications per user to 300 by introducing an async trim job that runs after each notification creation. This prevents unbounded notification growth that was causing DB CPU spikes. ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] This change requires a documentation update ## How Has This Been Tested? - Added unit tests for TrimUserNotificationsJob ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Implements a dedicated purge job to control notification volume and scheduling. > > - Introduces `Notification::RemoveOldNotificationJob` (queue: `purgable`) to delete notifications older than 1 month and trim each user to the 300 most recent (deterministic by `created_at DESC, id DESC`) > - Adds daily cron (`remove_old_notification_job` at 22:30 UTC, queue `purgable`) in `config/schedule.yml` > - Removes ad-hoc triggering of the purge from `TriggerScheduledItemsJob` > - Adds/updates specs covering enqueue queue, old-notification deletion, per-user trimming, and combined behavior > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9ea2b48. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com> Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
…ot#13356) Fixes chatwoot#13354 Fixes https://linear.app/chatwoot/issue/CW-6394/story-responses-are-not-being-shown-in-the-ui When someone replies to your Instagram story, agents in Chatwoot only see the reply text with no story image and no indication that it was a story reply. This makes it impossible to understand what the customer is responding to the message looks like a random text with no context. For example, if a customer replies "Love this!" to your story, the agent just sees "Love this!" with no way to know which story triggered the conversation. This PR fixes the issue by storing the story attachment and adding a context label. <img width="1408" height="2052" alt="CleanShot 2026-01-27 at 19 19 38@2x" src="https://github.com/user-attachments/assets/341afea9-98e3-4e47-b2fa-ef77fe32851f" />
## Description This PR includes cron job to delete the orphans ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Introduces a scheduled cleanup for conversations missing `contact` or `inbox`. > > - Adds `Internal::RemoveOrphanConversationsService` to batch-delete orphan conversations (scoped by optional `account`, within a configurable `days` window) with progress logging > - New `Internal::RemoveOrphanConversationsJob` that invokes the service; scheduled via `config/schedule.yml` to run every 12 hours on `housekeeping` queue > - Refactors rake task `chatwoot:ops:cleanup_orphan_conversations` to use the service and report `total_deleted` after confirmation > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 59a2471. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This PR adds the unsupported messages for tiktok. Fixes https://linear.app/chatwoot/issue/CW-6407/add-support-for-unsupported-message
Extracted hardcoded TikTok API version (`v1.3`) into a configurable `TIKTOK_API_VERSION` setting, consistent with how Instagram and WhatsApp handle API versions. Fixes https://linear.app/chatwoot/issue/CW-6408/tiktok-api-version-configurable-via-super-admin
The existing /api health check endpoint creates a new Redis connection
on every request and checks both Redis and Postgres availability. During
peak traffic, this creates unnecessary load and can cause cascading
failures when either service is slow - instances get marked unhealthy,
traffic shifts to remaining instances, which then also fail health
checks.
The new /health endpoint:
- Returns immediately with 200 {"status":"woot"}
- Skips all middleware and authentication
- No Redis or Postgres dependency
- Suitable for health checks that only need to verify the web server is
responding
…#13347) # Pull Request Template ## Description This PR includes the following updates: 1. Updated the design system color tokens by introducing new tokens for surfaces, overlays, buttons, labels, and cards, along with refinements to existing shades. 2. Refreshed both light and dark themes with adjusted background, border, and solid colors. 3. Replaced static Inter font files with the Inter variable font (including italic), supporting weights from 100–900. 4. Added custom font weights (420, 440, 460, 520) along with custom typography classes to enable more fine-grained and consistent typography control. ## Type of change - [x] New feature (non-breaking change which adds functionality) ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Pranav <pranav@chatwoot.com>
) ## Why We observed `Webhooks::TwilioEventsJob` failures ending up in Sidekiq dead jobs when Twilio callback payloads could not be mapped to a `Channel::TwilioSms` record. In this scenario, channel lookup raised `ActiveRecord::RecordNotFound`, which caused retries and eventual dead jobs instead of a graceful drop. Related Sentry issue/search: - https://chatwoot-p3.sentry.io/issues/?project=6382945&query=Webhooks%3A%3ATwilioEventsJob%20ActiveRecord%3A%3ARecordNotFound ## What changed This PR keeps the existing lookup flow but makes it non-raising: - `app/services/twilio/incoming_message_service.rb` - `find_by!` -> `find_by` for account SID + phone lookup - Added warning log when channel lookup misses - `app/services/twilio/delivery_status_service.rb` - `find_by!` -> `find_by` for account SID + phone lookup - Added warning log when channel lookup misses ## Reproduction Configure a Twilio webhook callback that reaches Chatwoot but does not match an existing Twilio channel lookup path. Before this change, the job raises `RecordNotFound` and can end up in dead jobs after retries. After this change, the job logs the miss and exits safely. ## Testing - `bundle exec rspec spec/services/twilio/incoming_message_service_spec.rb spec/services/twilio/delivery_status_service_spec.rb` - `bundle exec rubocop app/services/twilio/incoming_message_service.rb app/services/twilio/delivery_status_service.rb`
…t#13538) ## Summary Fix hardcoded `Chatwoot` branding in two UI tooltips using the existing `useBranding` flow so self-hosted/white-label deployments no longer show the wrong brand text. ## Changes - LabelSuggestion tooltip now uses: - `replaceInstallationName($t('LABEL_MGMT.SUGGESTIONS.POWERED_BY'))` - Message avatar tooltip (native app/external echo) now uses: - `replaceInstallationName(t('CONVERSATION.NATIVE_APP_ADVISORY'))` ## Why This follows the existing branding pattern already used in the product and keeps behavior consistent across deployments. ## Notes - No change to message logic or API behavior. - `AGENTS.md` updated with a branding guidance note. ## Fixes - Fixes chatwoot#13306 - Fixes chatwoot#13466 ## Testing <img width="195" height="155" alt="Screenshot 2026-02-13 at 3 55 39 PM" src="https://github.com/user-attachments/assets/5b295cdd-6e5d-42c0-bbd7-23ba7052e1c3" /> <img width="721" height="152" alt="Screenshot 2026-02-13 at 3 55 48 PM" src="https://github.com/user-attachments/assets/19cec2a0-451f-4fb3-bd61-7c2e591fc3c7" />
…oot#13353) ## Description Fixes a critical bug where conversations assigned to a team could be auto-assigned to agents outside that team when all team members were at capacity. ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes core assignment selection for both legacy and v2 flows; misconfiguration of `allow_auto_assign` or team membership could cause conversations to remain unassigned. > > **Overview** > Prevents auto-assignment from crossing team boundaries by filtering eligible agents to the conversation’s `team` members (and requiring `team.allow_auto_assign`) in both the legacy `AutoAssignmentHandler` path and the v2 `AutoAssignment::AssignmentService` (including the Enterprise override). > > Adds test coverage to ensure team-scoped conversations only assign to team members, and are skipped when team auto-assign is disabled or no team members are available; also updates the conversations controller spec setup to include team membership. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 67ed2bd. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…ed messages (chatwoot#13273) ## Description Handle messages with null content properly in UI and email notifications ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## Relevant Screenshots: <img width="688" height="765" alt="Screenshot 2026-01-21 at 4 43 00 PM" src="https://github.com/user-attachments/assets/6a27c22e-2ae6-4377-a05d-cfa44bf181fe" /> ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches notification email templates and message rendering conditions; mistakes could lead to missing content/attachments in emails or incorrect UI visibility, but changes are localized and non-auth/security related. > > **Overview** > Agent notification emails for *assigned* and *participating* new messages now include the actual message details (sender name, rendered text when present, and attachment links) and gracefully fall back when content is unavailable. > > To support this, the mailer now passes `@message` into Liquid via `MessageDrop` (adding `attachments` URLs), and the dashboard message UI now renders failed/external-error messages even when `content` is `null` while tightening retry eligibility to require content or attachments (and still within 1 day). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 475c8ce. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
…3487) ## Summary This PR reduces duplicate failure noise for audio transcription jobs that fail with permanent HTTP 400 responses, and fixes a file-format edge case causing intermittent 400s. Sentry issue: [CHATWOOT-99E / 6660541334](https://chatwoot-p3.sentry.io/issues/6660541334/) ## Confirmed root cause For some attachments, the stored filename had no extension (example: `speech`, content type `audio/mpeg`). When the temporary transcription upload file was created without an extension, OpenAI returned: `Unrecognized file format` (HTTP 400). ## Scope of changes 1. `Messages::AudioTranscriptionJob` - Keeps `discard_on Faraday::BadRequestError` to avoid retry storms on permanent request errors. - Adds explicit Rails warning logs for discarded jobs with attachment/job/status context. 2. `Messages::AudioTranscriptionService` - Keeps guaranteed temp file cleanup via `ensure`. - Ensures temp upload files include an extension when the original filename has none, derived from blob `content_type`. - This addresses intermittent failures like extensionless `audio/mpeg` files. ## Reproduction Enable audio transcription for an account and process an audio attachment whose stored filename has no extension (for example `speech`) but valid audio content type (`audio/mpeg`). Before this fix, OpenAI transcription could return HTTP 400 `Unrecognized file format` for that attachment while similar attachments with extensions succeeded. ## Testing Ran: `bundle exec rubocop enterprise/app/jobs/messages/audio_transcription_job.rb enterprise/app/services/messages/audio_transcription_service.rb` Result: both modified files pass lint with no offenses.
## Description
Adds missing analytics instrumentation for the editor AI funnel so we
can measure end-to-end usage and outcome quality.
### What was added
- Captain: Editor AI menu opened
- Captain: Generation failed
- Captain: AI-assisted message sent
### Behavior covered
- Tracks AI button click + menu open from both entry points:
- top panel sparkle button
- inline editor copilot button
- Tracks generation failures (initial + follow-up stages).
- Tracks whether accepted AI content was sent as-is or edited before
send.
### Notes
- Applies to editor Captain accept/send flow
(rewrite/summarize/reply_suggestion + follow-ups).
- Does not change Copilot sidebar flow instrumentation.
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update
## How Has This Been Tested?
### Manual verification steps
<img width="1906" height="832" alt="image"
src="https://github.com/user-attachments/assets/f0ade43b-aa8d-41be-8ca2-20a091a81f60"
/>
<img width="828" height="280" alt="image"
src="https://github.com/user-attachments/assets/be76219e-fb61-4a6e-bff5-dc085b0a3cc9"
/>
<img width="415" height="147" alt="image"
src="https://github.com/user-attachments/assets/36802c5c-33a7-49ed-bf7e-f0b02d86dccc"
/>
<img width="2040" height="516" alt="image"
src="https://github.com/user-attachments/assets/74b95288-bc86-4312-a282-14211ae8f25c"
/>
1. Open a conversation with Captain tasks enabled.
2. Click AI button in top panel and inline editor.
3. Confirm analytics events fire for:
- AI menu opened
4. Run an AI action and force a failure scenario (or empty response
path) and confirm generation-failed event.
5. Accept AI output, then:
- send without changes -> editedBeforeSend: false
- edit then send -> editedBeforeSend: true
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
# Pull Request Template ## Description Instruments captain v2 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Local testing: <img width="864" height="510" alt="image" src="https://github.com/user-attachments/assets/855ebce5-e8b8-4d22-b0bb-0d413769a6ab" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
Langfuse logging improvements ## Description Please include a summary of the change and issue(s) fixed. Also, mention relevant motivation, context, and any dependencies that this change requires. Fixes # (issue) For reply suggestion: the errors are being stored inside output field, but observations should be marked as errors. For assistant: add credit_used metadata to filter handoffs from ai-replies For langfuse tool call: add `observation_type=tool` ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. before: <img width="1028" height="57" alt="image" src="https://github.com/user-attachments/assets/70f6a36e-6c33-444c-a083-723c7c9e823a" /> after: <img width="872" height="69" alt="image" src="https://github.com/user-attachments/assets/1b6b6f5f-5384-4e9c-92ba-f56748fec6dd" /> `credit_used` to filter handoffs from AI replies that cause credit usage <img width="1082" height="672" alt="image" src="https://github.com/user-attachments/assets/90914227-553a-4c03-bc43-56b2018ac7c1" /> set `observation_type` to `tool` <img width="726" height="1452" alt="image" src="https://github.com/user-attachments/assets/e639cc9b-1c6c-4427-887e-23e5523bf64f" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules
…3525) # Pull Request Template ## Description This PR adds support for removing labels from the conversation card context menu. Assigned labels now show a checkmark, and clicking an already-selected label will remove it. Fixes https://linear.app/chatwoot/issue/CW-6400/allow-removing-labels-directly-from-the-right-click-menu chatwoot#13367 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? **Screencast** https://github.com/user-attachments/assets/4e3a6080-a67d-4851-9d10-d8dbf3ceeb04 ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules
…woot#13523) Some customers using WhatsApp inboxes with account-level webhooks were reporting receiving duplicate `message_created` webhook deliveries for every incoming message. Upon inspection, here's what we found - Both payloads are identical. - No errors appear in the application logs - Webhook URL is only configured in one place. This meant, the system was sending the webhooks twice. For some context, there's a know related issue... Meta's WhatsApp Business API can deliver the same webhook notification multiple times for a single message. The codebase already acknowledges this — there's a comment in `IncomingMessageBaseService#process_messages` noting that "multiple webhook events can be received against the same message due to misconfigurations in the Meta business manager account." A deduplication guard exists, but it doesn't actually work under concurrency. ### Rationale The existing dedup was a three-step sequence: check Redis (`GET`), check the database, then set a Redis flag (`SETEX`). Two Sidekiq workers processing duplicate Meta webhooks simultaneously would both complete the `GET` before either executed the `SETEX`, so both would proceed to create a message. The `source_id` column has a non-unique index, so the database wouldn't catch the duplicate either. Each message then independently fires `after_create_commit`, dispatching two `message_created` webhook events to the customer. ``` Worker A Worker B │ │ ▼ ▼ Redis GET key ──► nil Redis GET key ──► nil │ │ │ ◄── both pass guard ──► │ │ │ ▼ ▼ Redis SETEX key Redis SETEX key │ │ ▼ ▼ BEGIN transaction BEGIN transaction INSERT message INSERT message DELETE Redis key ◄─┐ │ COMMIT │ DELETE Redis key │ COMMIT │ │ └── key gone before ───┘ B's commit lands ▼ ▼ after_create_commit after_create_commit dispatch MESSAGE_CREATED dispatch MESSAGE_CREATED │ │ ▼ ▼ WebhookJob ──► n8n WebhookJob ──► n8n (duplicate!) ``` There was a second, subtler problem visible in the diagram: the Redis key was cleared *inside* the database transaction, before the transaction committed. This opened a window where neither the Redis check nor the database check would see the in-flight message. The fix collapses the check-and-set into a single `SET NX EX` call, which is atomic in Redis. The key is no longer eagerly cleared — it expires naturally after 24 hours. The database lookup (`find_message_by_source_id`) remains as a fallback for messages that were created before the lock expired. ``` Worker A Worker B │ │ ▼ ▼ Redis SET NX ──► OK Redis SET NX ──► nil │ │ ▼ ▼ proceeds to create returns early message normally (lock already held) ``` ### Implementation Notes The lock logic is extracted into `Whatsapp::MessageDedupLock`, a small class that wraps a single `Redis SET NX EX` call. This makes the concurrency guarantee testable in isolation — the spec uses a `CyclicBarrier` to race two threads against the same key and asserts exactly one wins, without needing database writes, `use_transactional_tests = false`, or monkey-patching. Because the Redis lock now persists (instead of being cleared mid-transaction), existing WhatsApp specs needed an `after` hook to clean up `MESSAGE_SOURCE_KEY::*` keys between examples. Transactional fixtures only roll back the database, not Redis.
# Pull Request Template ## Description ## Type of change typo fix ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules
…oot#13506) # Pull Request Template ## Description This PR includes: 1. Removes multiselect usage from the Merge Contact modal (Conversation sidebar) and replaces it with the existing component used on the Contact Details page. 2. Replaces legacy form and multiselect elements in Add and Edit automations flows with next components.**(Also check Macros)** 3. Replace multiselect with ComboBox in contact form country field. 4. Replace multiselect with TagInput in create/edit attribute form. 5. Replace multiselect with TagInput for agent selection in inbox creation. 6. Replace multiselect with ComboBox in Facebook channel page selection ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? **Screenshots** 1. **Merge modal** <img width="741" height="449" alt="image" src="https://github.com/user-attachments/assets/a05a96ec-0692-4d94-9e27-d3e85fd143e4" /> <img width="741" height="449" alt="image" src="https://github.com/user-attachments/assets/fc1dc977-689d-4440-869d-2124e4ca9083" /> 2. **Automations** <img width="849" height="1089" alt="image" src="https://github.com/user-attachments/assets/b0155f06-ab21-4f90-a2c8-5bfbd97b08f7" /> <img width="813" height="879" alt="image" src="https://github.com/user-attachments/assets/0921ac4a-88f5-49ac-a776-cc02941b479c" /> <img width="849" height="826" alt="image" src="https://github.com/user-attachments/assets/44358dae-a076-4e10-b7ba-a4e40ccd817f" /> 3. **Country field** <img width="462" height="483" alt="image" src="https://github.com/user-attachments/assets/d5db9aa1-b859-4327-9960-957d7091678f" /> 4. **Add/Edit attribute form** <img width="619" height="646" alt="image" src="https://github.com/user-attachments/assets/6ab2ea94-73e5-40b8-ac29-399c0543fa7b" /> <img width="619" height="646" alt="image" src="https://github.com/user-attachments/assets/b4c5bb0e-baa0-4ef7-a6a2-adb0f0203243" /> <img width="635" height="731" alt="image" src="https://github.com/user-attachments/assets/74890c80-b213-4567-bf5f-4789dda39d2d" /> 5. **Agent selection in inbox creation** <img width="635" height="534" alt="image" src="https://github.com/user-attachments/assets/0003bad1-1a75-4f20-b014-587e1c19a620" /> <img width="809" height="602" alt="image" src="https://github.com/user-attachments/assets/5e7ab635-7340-420a-a191-e6cd49c02704" /> 7. **Facebook channel page selection** <img width="597" height="444" alt="image" src="https://github.com/user-attachments/assets/f7ec8d84-0a7d-4bc6-92a1-a1365178e319" /> <img width="597" height="444" alt="image" src="https://github.com/user-attachments/assets/d0596c4d-94c1-4544-8b50-e7103ff207a6" /> <img width="597" height="444" alt="image" src="https://github.com/user-attachments/assets/be097921-011b-4dbe-b5f1-5d1306e25349" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
…on (chatwoot#13549) Fixes https://linear.app/chatwoot/issue/CW-6494/add-shopify-mandatory-compliance-webhooks-for-app-store-listing Shopify requires all public apps to handle three GDPR compliance webhooks before they can be listed on the App Store. Their automated review checks for these endpoints and verifies that apps validate HMAC signatures on incoming requests. We were failing both checks. This PR adds a single webhook endpoint at `POST /webhooks/shopify` that receives all three compliance events. When Shopify sends a webhook, it signs the payload with our app's client secret and includes the signature in the `X-Shopify-Hmac-SHA256` header. Our controller reads the raw body, computes the expected HMAC-SHA256 digest, and rejects mismatched requests with a 401. Shopify identifies the event type through the `X-Shopify-Topic` header. For `customers/data_request` and `customers/redact`, we simply acknowledge with a 200—Chatwoot doesn't persist any Shopify customer data. All order lookups happen as live API calls at query time. For `shop/redact`, which Shopify sends after a merchant uninstalls the app, we delete the integration hook for that shop domain and remove the stored access token and configuration. ### How to test via Rails console ``` secret = GlobalConfigService.load('SHOPIFY_CLIENT_SECRET', nil) body = '{"shop_domain":"test.myshopify.com"}' valid_hmac = Base64.strict_encode64(OpenSSL::HMAC.digest('SHA256', secret, body)) ``` #### Test 1: No HMAC → 401 ``` app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Topic' => 'customers/data_request' } app.response.code # => "401" ``` #### Test 2: Invalid HMAC → 401 ``` app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => 'invalid', 'X-Shopify-Topic' => 'customers/data_request' } app.response.code # => "401" ``` #### Test 3: Valid HMAC, customers/data_request → 200 ``` app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'customers/data_request' } app.response.code # => "200" ``` #### Test 4: Valid HMAC, customers/redact → 200 ``` app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'customers/redact' } app.response.code # => "200" ``` #### Test 5: Valid HMAC, shop/redact → 200 (deletes hook) ``` # First check if a hook exists for this domain: Integrations::Hook.where(app_id: 'shopify', reference_id: 'test.myshopify.com').count app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'shop/redact' } app.response.code # => "200" ``` --------- Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
# Pull Request Template ## Description Please include a summary of the change and issue(s) fixed. Also, mention relevant motivation, context, and any dependencies that this change requires. Fixes: The LLM call was wrapped in a transaction. This is an anti-pattern and caused idle-connections which PG eventually terminated with `PQconsumeInput() FATAL: terminating connection due to idle-in-transaction timeout` This resulted in activity messages being missing in some conversations on captain handoff, failures queueing up for retry and captain responding long after conversation was marked open/snoozed. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. locally and specs ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Bumps [rack](https://github.com/rack/rack) from 3.2.3 to 3.2.5. <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/rack/rack/blob/main/CHANGELOG.md">rack's changelog</a>.</em></p> <blockquote> <h1>Changelog</h1> <p>All notable changes to this project will be documented in this file. For info on how to format all future additions to this file please reference <a href="https://keepachangelog.com/en/1.0.0/">Keep A Changelog</a>.</p> <h2>Unreleased</h2> <h3>Security</h3> <ul> <li><a href="https://github.com/advisories/GHSA-r657-rxjc-j557">CVE-2025-61780</a> Improper handling of headers in <code>Rack::Sendfile</code> may allow proxy bypass.</li> <li><a href="https://github.com/advisories/GHSA-6xw4-3v39-52mm">CVE-2025-61919</a> Unbounded read in <code>Rack::Request</code> form parsing can lead to memory exhaustion.</li> <li><a href="https://github.com/advisories/GHSA-whrj-4476-wvmp">CVE-2026-25500</a> XSS injection via malicious filename in <code>Rack::Directory</code>.</li> <li><a href="https://github.com/advisories/GHSA-mxw3-3hh2-x2mh">CVE-2026-22860</a> Directory traversal via root prefix bypass in <code>Rack::Directory</code>.</li> </ul> <h3>SPEC Changes</h3> <ul> <li>Define <code>rack.response_finished</code> callback arguments more strictly. (<a href="https://redirect.github.com/rack/rack/pull/2365">#2365</a>, <a href="https://github.com/skipkayhil"><code>@skipkayhil</code></a>)</li> </ul> <h3>Added</h3> <ul> <li>Add <code>Rack::Files#assign_headers</code> to allow overriding how the configured file headers are set. (<a href="https://redirect.github.com/rack/rack/pull/2377">#2377</a>, <a href="https://github.com/codergeek121"><code>@codergeek121</code></a>)</li> <li>Add support for <code>rack.response_finished</code> to <code>Rack::TempfileReaper</code>. (<a href="https://redirect.github.com/rack/rack/pull/2363">#2363</a>, <a href="https://github.com/skipkayhil"><code>@skipkayhil</code></a>)</li> <li>Add support for streaming bodies when using <code>Rack::Events</code>. (<a href="https://redirect.github.com/rack/rack/blob/main/redirect.github.com/rack/rack/pull/2375">#2375</a>, <a href="https://github.com/unflxw"><code>@unflxw</code></a>)</li> <li>Add <code>deflaters</code> option to <code>Rack::Deflater</code> to enable custom compression algorithms like zstd. (<a href="https://redirect.github.com/rack/rack/issues/2168">#2168</a>, <a href="https://github.com/alexanderadam"><code>@alexanderadam</code></a>)</li> <li>Add <code>Rack::Request#prefetch?</code> for identifying requests with <code>Sec-Purpose: prefetch</code> header set. (<a href="https://redirect.github.com/rack/rack/pull/2405">#2405</a>, <a href="https://github.com/glaszig"><code>@glaszig</code></a>)</li> <li>Add <code>rack.request.trusted_proxy</code> environment key to indicate whether the request is coming from a trusted proxy.</li> </ul> <h3>Changed</h3> <ul> <li>Raise before exceeding a part limit, not after. (<a href="https://redirect.github.com/rack/rack/pull/2362">#2362</a>, <a href="https://github.com/matthew-puku"><code>@matthew-puku</code></a>)</li> <li>Rack::Deflater now uses a fixed GZip mtime value. (<a href="https://redirect.github.com/rack/rack/pull/2372">#2372</a>, <a href="https://github.com/bensheldon"><code>@bensheldon</code></a>)</li> <li>Multipart parser drops support for RFC 2231 <code>filename*</code> parameter (prohibited by RFC 7578) and now properly handles UTF-8 encoded filenames via percent-encoding and direct UTF-8 bytes. (<a href="https://redirect.github.com/rack/rack/pull/2398">#2398</a>, <a href="https://github.com/wtn"><code>@wtn</code></a>)</li> <li>The query parser now raises <code>Rack::QueryParser::IncompatibleEncodingError</code> if we try to parse params that are not ASCII compatible. (<a href="https://redirect.github.com/rack/rack/pull/2416">#2416</a>, <a href="https://github.com/bquorning"><code>@bquorning</code></a>)</li> </ul> <h3>Fixed</h3> <ul> <li>Multipart parser: limit MIME header size check to the unread buffer region to avoid false <code>multipart mime part header too large</code> errors when previously read data accumulates in the scan buffer. (<a href="https://redirect.github.com/rack/rack/pull/2392">#2392</a>, <a href="https://github.com/alpaca-tc"><code>@alpaca-tc</code></a>, <a href="https://github.com/willnet"><code>@willnet</code></a>, <a href="https://github.com/krororo"><code>@krororo</code></a>)</li> <li>Fix <code>Rack::MockResponse#body</code> when the body is a Proc. (<a href="https://redirect.github.com/rack/rack/pull/2420">#2420</a>, <a href="https://redirect.github.com/rack/rack/pull/2423">#2423</a>, <a href="https://github.com/tavianator"><code>@tavianator</code></a>, [<a href="https://github.com/ioquatix"><code>@ioquatix</code></a>])</li> </ul> <h2>[3.2.4] - 2025-11-03</h2> <h3>Fixed</h3> <ul> <li>Multipart parser: limit MIME header size check to the unread buffer region to avoid false <code>multipart mime part header too large</code> errors when previously read data accumulates in the scan buffer. (<a href="https://redirect.github.com/rack/rack/pull/2392">#2392</a>, <a href="https://github.com/alpaca-tc"><code>@alpaca-tc</code></a>, <a href="https://github.com/willnet"><code>@willnet</code></a>, <a href="https://github.com/krororo"><code>@krororo</code></a>)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/rack/rack/commit/bb5f3555bd12b9065112353e829298b3b5623ceb"><code>bb5f355</code></a> Bump patch version.</li> <li><a href="https://github.com/rack/rack/commit/f9bde3bc2dde2771185ac1a7b7602a4d9fa0a0d8"><code>f9bde3b</code></a> Prevent directory traversal via root prefix bypass.</li> <li><a href="https://github.com/rack/rack/commit/93a68f58aa82aa48f09b751501f19f5e760dd406"><code>93a68f5</code></a> XSS injection via malicious filename in <code>Rack::Directory</code>.</li> <li><a href="https://github.com/rack/rack/commit/3b8b0d22d68a7fb30fdea40f838d0f95a05c134d"><code>3b8b0d2</code></a> Fix MockResponse#body when the body is a Proc (<a href="https://redirect.github.com/rack/rack/issues/2420">#2420</a>)</li> <li><a href="https://github.com/rack/rack/commit/4c24539777db8833d78f881680cd245878cfba31"><code>4c24539</code></a> Bump patch version.</li> <li><a href="https://github.com/rack/rack/commit/3ba5e4f22f55abac21037bb137e56e5c8e36b673"><code>3ba5e4f</code></a> Allow Multipart head to span read boundary. (<a href="https://redirect.github.com/rack/rack/issues/2392">#2392</a>)</li> <li>See full diff in <a href="https://github.com/rack/rack/compare/v3.2.3...v3.2.5">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/chatwoot/chatwoot/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Merges upstream 4.11.0, introducing new reporting endpoints/helpers, conversation workflow settings, Captain-related UI changes, contact list pagination enhancements, and broad UI theming updates.
Changes:
- Updates Vuex conversation/contact modules (conversationId-scoped mutations, contact append +
has_more). - Adds new reports builders/endpoints and settings routes (conversation workflow, outgoing message counts, matrix + distribution reports).
- Migrates/refreshes UI components and styling (surface tokens, ComboBox usage, sidebar improvements, Captain UX).
Reviewed changes
Copilot reviewed 286 out of 503 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| app/javascript/dashboard/store/modules/labels.js | Adjusts label getter to return a default object when not found |
| app/javascript/dashboard/store/modules/conversations/index.js | Makes several mutations conversationId-scoped and adds SET_CHAT_DATA_FETCHED |
| app/javascript/dashboard/store/modules/conversations/actions.js | Updates commits/dispatches to pass conversationId and adds custom-attributes update on toggle |
| app/javascript/dashboard/store/modules/contacts/mutations.js | Adds hasMore meta handling and APPEND_CONTACTS mutation |
| app/javascript/dashboard/store/modules/contacts/index.js | Adds meta.hasMore to contacts state |
| app/javascript/dashboard/store/modules/contacts/actions.js | Adds append option to search and uses append mutation |
| app/javascript/dashboard/routes/dashboard/settings/settings.routes.js | Adds conversation workflow settings routes |
| app/javascript/dashboard/routes/dashboard/settings/reports/helpers/reportFilterHelper.js | Adds helpers to parse/generate report/filter URL params |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersTeams.spec.js | Removes legacy report filter spec |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersRatings.spec.js | Removes legacy report filter spec |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersLabels.spec.js | Removes legacy report filter spec |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersInboxes.spec.js | Removes legacy report filter spec |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersDateRange.spec.js | Removes legacy report filter spec |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersDateGroupBy.spec.js | Removes legacy report filter spec |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/Filters/FiltersAgents.spec.js | Removes legacy report filter spec |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportsWrapper.vue | Updates wrapper background token |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/v3/ActiveFilterChip.vue | Makes id nullable and adds showClearFilter prop passthrough |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Teams.vue | Removes legacy filter component |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Ratings.vue | Removes legacy filter component |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Labels.vue | Removes legacy filter component |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Inboxes.vue | Removes legacy filter component |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/DateRange.vue | Removes legacy filter component |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/DateGroupBy.vue | Removes legacy filter component |
| app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Agents.vue | Removes legacy filter component |
| app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue | Adds spacing styles to report container |
| app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue | Replaces ReportFilterSelector with ReportFilters |
| app/javascript/dashboard/routes/dashboard/settings/reports/CsatResponses.vue | Dispatches agents fetch on mount |
| app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue | Replaces ReportFilterSelector with ReportFilters |
| app/javascript/dashboard/routes/dashboard/settings/profile/Wrapper.vue | Updates settings background token |
| app/javascript/dashboard/routes/dashboard/settings/macros/MacroNodes.vue | Updates text color token |
| app/javascript/dashboard/routes/dashboard/settings/inbox/components/BusinessDay.vue | Updates text color token |
| app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue | Replaces multiselect with ComboBox for page selection |
| app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue | Adjusts tabs wrapper positioning classes |
| app/javascript/dashboard/routes/dashboard/settings/conversationWorkflow/index.vue | Adds Conversation Workflow settings page |
| app/javascript/dashboard/routes/dashboard/settings/conversationWorkflow/conversationWorkflow.routes.js | Adds Conversation Workflow route config |
| app/javascript/dashboard/routes/dashboard/settings/components/BaseSettingsHeader.vue | Updates link color token |
| app/javascript/dashboard/routes/dashboard/settings/automation/constants.js | Adds pending conversation automation action |
| app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue | Migrates add/edit modals to dialog refs and reduces state flags |
| app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentCapacityEditPage.vue | Adds async error handling + alerts around CRUD operations |
| app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentAssignmentCreatePage.vue | Adds inboxId query support for breadcrumb/navigation + forwards query |
| app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/assignmentPolicy.routes.js | Switches feature flag to ADVANCED_ASSIGNMENT |
| app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/Index.vue | Feature-gates Agent Capacity card by account flags |
| app/javascript/dashboard/routes/dashboard/settings/account/components/AutoResolve.vue | Replaces SectionLayout structure with inline layout |
| app/javascript/dashboard/routes/dashboard/settings/account/Index.vue | Removes AutoResolve config from account settings index |
| app/javascript/dashboard/routes/dashboard/settings/Wrapper.vue | Updates wrapper background token |
| app/javascript/dashboard/routes/dashboard/settings/SettingsWrapper.vue | Updates settings wrapper background token |
| app/javascript/dashboard/routes/dashboard/settings/SettingsHeader.vue | Updates header background token |
| app/javascript/dashboard/routes/dashboard/inbox/helpers/InboxViewHelpers.js | Updates notification icon color token |
| app/javascript/dashboard/routes/dashboard/inbox/components/MenuItem.vue | Updates hover text color token |
| app/javascript/dashboard/routes/dashboard/inbox/components/InboxItemHeader.vue | Adds background token to header container |
| app/javascript/dashboard/routes/dashboard/inbox/components/InboxDisplayMenu.vue | Updates active state color token |
| app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue | Updates inbox panel background token and selection rounding |
| app/javascript/dashboard/routes/dashboard/inbox/InboxEmptyState.vue | Updates empty state background token |
| app/javascript/dashboard/routes/dashboard/helpcenter/pages/PortalsIndexPage.vue | Updates loading background token |
| app/javascript/dashboard/routes/dashboard/helpcenter/pages/HelpCenterPageRouteView.vue | Updates page background token |
| app/javascript/dashboard/routes/dashboard/helpcenter/components/UpgradePage.vue | Updates upgrade page background token |
| app/javascript/dashboard/routes/dashboard/conversation/customAttributes/CustomAttributes.vue | Adjusts attribute presence check + updates zebra background tokens |
| app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue | Migrates merge modal to ref-based open |
| app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue | Replaces multiselect country selector with ComboBox and adds TikTok profile field |
| app/javascript/dashboard/routes/dashboard/conversation/ConversationParticipant.vue | Removes explicit background class |
| app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue | Updates assignee dispatch payload and removes explicit background class |
| app/javascript/dashboard/routes/dashboard/contacts/pages/ContactManageView.vue | Updates page background token |
| app/javascript/dashboard/routes/dashboard/captain/pages/CaptainPageRouteView.vue | Updates page background token |
| app/javascript/dashboard/routes/dashboard/captain/pages/AssistantsIndexPage.vue | Updates loading background token |
| app/javascript/dashboard/routes/dashboard/captain/assistants/inboxes/Index.vue | Refetches inboxes when assistantId changes (watch) |
| app/javascript/dashboard/routes/dashboard/campaigns/pages/CampaignsPageRouteView.vue | Updates page background token |
| app/javascript/dashboard/routes/dashboard/Dashboard.vue | Adds main background token |
| app/javascript/dashboard/modules/search/components/SearchView.vue | Updates page background token |
| app/javascript/dashboard/modules/search/components/SearchInput.vue | Updates focus color token |
| app/javascript/dashboard/modules/contact/components/ContactDropdownItem.vue | Removes legacy dropdown item component |
| app/javascript/dashboard/i18n/locale/en/signup.json | Adds new signup CTA i18n key |
| app/javascript/dashboard/i18n/locale/en/report.json | Adds filter placeholders and copy tweaks |
| app/javascript/dashboard/i18n/locale/en/integrations.json | Expands Captain/OpenAI option labels and reply options |
| app/javascript/dashboard/i18n/locale/en/inboxMgmt.json | Adds assignment policy UI copy |
| app/javascript/dashboard/i18n/locale/en/general.json | Adds generic buttons + choice toggle labels |
| app/javascript/dashboard/i18n/locale/en/datePicker.json | Adds new date picker labels/options |
| app/javascript/dashboard/i18n/locale/en/conversation.json | Adds unsupported TikTok message, label removal messages, restricted messaging, copilot copy |
| app/javascript/dashboard/i18n/locale/en/contact.json | Adds “Load more” label |
| app/javascript/dashboard/i18n/locale/en/bulkActions.json | Adds resolve-required-attributes bulk action copy |
| app/javascript/dashboard/i18n/locale/en/automation.json | Adds pending conversation automation label |
| app/javascript/dashboard/i18n/locale/en/attributesMgmt.json | Adds badges labels |
| app/javascript/dashboard/helper/validations.js | Allows pending_conversation action |
| app/javascript/dashboard/helper/specs/editorHelper.spec.js | Removes channel-specific formatting tests |
| app/javascript/dashboard/helper/editorHelper.js | Adds showCaptain flag and hides copilot menu by default |
| app/javascript/dashboard/helper/automationHelper.js | Returns cloned defaults to avoid shared state mutation |
| app/javascript/dashboard/helper/AnalyticsHelper/events.js | Introduces CAPTAIN_EVENTS and replaces legacy OpenAI events usage |
| app/javascript/dashboard/featureFlags.js | Adds advanced assignment, captain tasks, required attributes flags |
| app/javascript/dashboard/constants/globals.js | Updates testimonial URL |
| app/javascript/dashboard/constants/editor.js | Adds copilot menu key to multiple contexts |
| app/javascript/dashboard/composables/utils/useKbd.js | Improves Mac detection, updates enter glyph, exports getModifierKey |
| app/javascript/dashboard/composables/useLabelSuggestions.js | Adds label suggestion composable using Captain tasks API |
| app/javascript/dashboard/composables/useConversationRequiredAttributes.js | Adds required-attributes workflow composable |
| app/javascript/dashboard/composables/useAutomation.js | Makes automation types reactive and dedupes injected custom-attr conditions |
| app/javascript/dashboard/composables/commands/useConversationHotKeys.js | Switches AI gating to Captain tasks and changes rewrite action keys/labels |
| app/javascript/dashboard/composables/commands/spec/useConversationHotKeys.spec.js | Updates tests to mock useCaptain instead of useAI |
| app/javascript/dashboard/composables/captain/constants.js | Adds Captain error types/constants |
| app/javascript/dashboard/components/widgets/conversation/linear/CreateOrLinkIssue.vue | Adjusts tabs height |
| app/javascript/dashboard/components/widgets/conversation/copilot/CaptainLoader.vue | Adds new Captain loader animation component |
| app/javascript/dashboard/components/widgets/conversation/conversation/LabelSuggestion.vue | Switches integration gating to Captain tasks + branding replacement + events |
| app/javascript/dashboard/components/widgets/conversation/contextMenu/menuItem.vue | Adds check icon for already-assigned labels |
| app/javascript/dashboard/components/widgets/conversation/contextMenu/Index.vue | Adds label removal support in menu |
| app/javascript/dashboard/components/widgets/conversation/advancedFilterItems/languages.js | Adds Portuguese (Brazil) locale option |
| app/javascript/dashboard/components/widgets/conversation/ReplyBoxBanner.vue | Updates assignee dispatch payload |
| app/javascript/dashboard/components/widgets/conversation/OnboardingFeatureCard.vue | Updates card background token |
| app/javascript/dashboard/components/widgets/conversation/MessageSignatureMissingAlert.vue | Removes horizontal margin class |
| app/javascript/dashboard/components/widgets/conversation/EmptyState/EmptyState.vue | Adds background token to empty state |
| app/javascript/dashboard/components/widgets/conversation/CopilotEditorSection.vue | Adds copilot editor section w/ loading state |
| app/javascript/dashboard/components/widgets/conversation/ConversationSidebar.vue | Updates sidebar background token |
| app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue | Removes border/background classes and adjusts padding |
| app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue | Adds label removal emit + context menu label state |
| app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue | Updates background and tabs styling |
| app/javascript/dashboard/components/widgets/WootWriter/EditorModeToggle.vue | Forces private-note mode when replies are restricted |
| app/javascript/dashboard/components/widgets/WootWriter/CopilotReplyBottomPanel.vue | Adds bottom panel for accepting/discarding copilot content |
| app/javascript/dashboard/components/widgets/ChatTypeTabs.vue | Adjusts tabs height |
| app/javascript/dashboard/components/widgets/AutomationFileInput.vue | Tweaks input styling |
| app/javascript/dashboard/components/widgets/AutomationActionTeamMessageInput.vue | Migrates team selector to new MultiSelect and cleans styling |
| app/javascript/dashboard/components/widgets/AttachmentsPreview.vue | Improves attachment list layout wrapping |
| app/javascript/dashboard/components/widgets/AIAssistanceCTAButton.vue | Removes legacy AI assistance CTA button |
| app/javascript/dashboard/components/ui/Tabs/TabsItem.vue | Restyles tabs and badge appearance |
| app/javascript/dashboard/components/ui/Dropdown/DropdownSearch.vue | Uses type="search" and outline override |
| app/javascript/dashboard/components/ui/DatePicker/components/CalendarYear.vue | Stops click propagation on year select |
| app/javascript/dashboard/components/ui/DatePicker/components/CalendarWeek.vue | Updates “today” outline text color token |
| app/javascript/dashboard/components/ui/DatePicker/components/CalendarMonth.vue | Stops click propagation on month select |
| app/javascript/dashboard/components/ui/DatePicker/components/CalendarFooter.vue | Removes ghost prop on apply button |
| app/javascript/dashboard/components/ui/DatePicker/components/CalendarDateRange.vue | Adjusts typography and adds separator rendering |
| app/javascript/dashboard/components/ui/DatePicker/components/CalendarAction.vue | Stops click propagation on navigation |
| app/javascript/dashboard/components/copilot/CopilotContainer.vue | Updates copilot panel background token |
| app/javascript/dashboard/components/ConversationItem.vue | Adds removeLabels wiring |
| app/javascript/dashboard/components/ChatListHeader.vue | Adjusts header height |
| app/javascript/dashboard/components/Accordion/AccordionItem.vue | Updates icon text color and removes background on body |
| app/javascript/dashboard/components-next/textarea/TextArea.vue | Emits focus/blur events in addition to model updates |
| app/javascript/dashboard/components-next/taginput/helper/tagInputHelper.js | Adds allowCreate and skipLabelDedup options |
| app/javascript/dashboard/components-next/taginput/helper/spec/tagInputHelper.spec.js | Adds tests for new tag menu behavior flags |
| app/javascript/dashboard/components-next/tabbar/TabBar.vue | Updates active text color token |
| app/javascript/dashboard/components-next/switch/Switch.vue | Tweaks animation easing |
| app/javascript/dashboard/components-next/sidebar/provider.js | Adds sidebar resize + shared popover state management |
| app/javascript/dashboard/components-next/sidebar/SidebarSubGroup.vue | Adds min-width constraints and adjusts gradient token |
| app/javascript/dashboard/components-next/sidebar/SidebarProfileMenu.vue | Adds collapsed rendering mode |
| app/javascript/dashboard/components-next/sidebar/SidebarGroupLeaf.vue | Adjusts spacing and active styling |
| app/javascript/dashboard/components-next/sidebar/SidebarGroupHeader.vue | Restyles group header and badge |
| app/javascript/dashboard/components-next/sidebar/SidebarChangelogCard.vue | Exposes loading/data and forwards attrs to support popover button |
| app/javascript/dashboard/components-next/sidebar/SidebarChangelogButton.vue | Adds changelog popover trigger button |
| app/javascript/dashboard/components-next/sidebar/SidebarAccountSwitcher.vue | Adds collapsed logo trigger and adjusts dropdown rendering |
| app/javascript/dashboard/components-next/sidebar/MobileSidebarLauncher.vue | Tweaks transition easing |
| app/javascript/dashboard/components-next/sidebar/ChannelLeaf.vue | Removes active background usage in icon container |
| app/javascript/dashboard/components-next/pagination/PaginationFooter.vue | Updates backdrop gradient token |
| app/javascript/dashboard/components-next/message/constants.js | Adds IG_STORY_REPLY attachment type |
| app/javascript/dashboard/components-next/message/bubbles/Unsupported.vue | Shows channel-specific “unsupported” messages (incl. TikTok) |
| app/javascript/dashboard/components-next/message/bubbles/Template/CallToAction.vue | Updates button text color token |
| app/javascript/dashboard/components-next/message/bubbles/Template/CSAT.vue | Updates button text color token |
| app/javascript/dashboard/components-next/message/bubbles/InstagramStory.vue | Adds story-reply detection and label |
| app/javascript/dashboard/components-next/message/bubbles/Base.vue | Formats and sanitizes reply preview content |
| app/javascript/dashboard/components-next/message/MessageList.vue | Updates list background token |
| app/javascript/dashboard/components-next/message/MessageError.vue | Disables retry when there’s no content/attachments or after 1 day |
| app/javascript/dashboard/components-next/input/ChoiceToggle.vue | Adds new boolean choice toggle input component |
| app/javascript/dashboard/components-next/filter/operators.js | Updates operator icon color token |
| app/javascript/dashboard/components-next/filter/inputs/SingleSelect.vue | Adds dropdown height, deselect disable, and selected null guard |
| app/javascript/dashboard/components-next/filter/inputs/MultiSelect.vue | Adds dropdown height prop support |
| app/javascript/dashboard/components-next/filter/inputs/FilterSelect.vue | Guards missing options and supports disabled “header” rows |
| app/javascript/dashboard/components-next/filter/ConditionRow.vue | Adds null-safe operator lookup and exposes resetValidation |
| app/javascript/dashboard/components-next/dropdown-menu/base/DropdownSection.vue | Adds configurable height prop |
| app/javascript/dashboard/components-next/dialog/Dialog.vue | Adds position option and prevents slot rendering when closed |
| app/javascript/dashboard/components-next/combobox/ComboBox.vue | Allows deselect by selecting the same option |
| app/javascript/dashboard/components-next/changelog-card/StackedChangelogCard.vue | Updates card background token |
| app/javascript/dashboard/components-next/captain/pageComponents/response/ResponseForm.vue | Updates button text color token |
| app/javascript/dashboard/components-next/captain/pageComponents/inbox/ConnectInboxForm.vue | Updates button text color token |
| app/javascript/dashboard/components-next/captain/pageComponents/document/RelatedResponses.vue | Adds total count to dialog title |
| app/javascript/dashboard/components-next/captain/pageComponents/document/DocumentForm.vue | Updates button text color token |
| app/javascript/dashboard/components-next/captain/pageComponents/customTool/CustomToolForm.vue | Updates button text color token |
| app/javascript/dashboard/components-next/captain/pageComponents/assistant/AssistantForm.vue | Updates button text color token |
| app/javascript/dashboard/components-next/captain/assistant/AddNewScenariosDialog.vue | Updates button text color token |
| app/javascript/dashboard/components-next/captain/PageLayout.vue | Updates page background token |
| app/javascript/dashboard/components-next/button/Button.vue | Updates blue color tokens and slate solid background token |
| app/javascript/dashboard/components-next/NewConversation/components/EmailOptions.vue | Enables tag input create for CC/BCC |
| app/javascript/dashboard/components-next/NewConversation/components/ComposeNewConversationForm.vue | Refactors to accept external formState |
| app/javascript/dashboard/components-next/Inbox/InboxCard.vue | Updates notification color fallback token |
| app/javascript/dashboard/components-next/HelpCenter/Pages/CategoryPage/CategoryForm.vue | Updates button text color token |
| app/javascript/dashboard/components-next/HelpCenter/LocaleCard/LocaleCard.vue | Updates “default” chip text color token |
| app/javascript/dashboard/components-next/HelpCenter/HelpCenterLayout.vue | Updates page background token |
| app/javascript/dashboard/components-next/HelpCenter/CategoryCard/CategoryCard.vue | Updates hover link color token |
| app/javascript/dashboard/components-next/HelpCenter/ArticleCard/ArticleCard.vue | Updates hover link color token |
| app/javascript/dashboard/components-next/EmptyStateLayout.vue | Updates backdrop gradient and typography classes |
| app/javascript/dashboard/components-next/CustomAttributes/OtherAttribute.vue | Updates link color token |
| app/javascript/dashboard/components-next/ConversationWorkflow/constants.js | Adds attribute type constants |
| app/javascript/dashboard/components-next/ConversationWorkflow/ConversationRequiredAttributeItem.vue | Adds required attribute item display component |
| app/javascript/dashboard/components-next/Conversation/ConversationRequiredEmpty.vue | Adds empty state for required attributes |
| app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactMerge.vue | Updates button text color token |
| app/javascript/dashboard/components-next/Contacts/ContactsLoadMore.vue | Adds “Load more” component for contacts infinite mode |
| app/javascript/dashboard/components-next/Contacts/ContactsListLayout.vue | Adds infinite mode footer and loadMore event |
| app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactListHeaderWrapper.vue | Simplifies create-contact dialog open logic |
| app/javascript/dashboard/components-next/Contacts/ContactsForm/ContactImportDialog.vue | Updates link color token |
| app/javascript/dashboard/components-next/Contacts/ContactsDetailsLayout.vue | Updates page background token |
| app/javascript/dashboard/components-next/Companies/CompaniesListLayout.vue | Updates page background token |
| app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/WhatsAppCampaign/WhatsAppCampaignForm.vue | Updates button text color token |
| app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/SMSCampaign/SMSCampaignForm.vue | Updates button text color token |
| app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/LiveChatCampaign/LiveChatCampaignForm.vue | Updates button text color token |
| app/javascript/dashboard/components-next/Campaigns/CampaignLayout.vue | Updates page background token |
| app/javascript/dashboard/components-next/AssignmentPolicy/components/story/BaseInfo.story.vue | Removes enabled/status props from story |
| app/javascript/dashboard/components-next/AssignmentPolicy/components/RadioCard.vue | Adds disabled state and premium badge UI |
| app/javascript/dashboard/components-next/AssignmentPolicy/components/FairDistribution.vue | Fixes duration unit conversion (seconds↔minutes) |
| app/javascript/dashboard/components-next/AssignmentPolicy/components/ExclusionRules.vue | Updates button text color token |
| app/javascript/dashboard/components-next/AssignmentPolicy/components/DataTable.vue | Adds navigate event and row navigation UX |
| app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentPolicyCard/AssignmentPolicyCard.vue | Removes enabled badge from card header |
| app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentPolicyCard/AssignmentPolicyCard.story.vue | Removes enabled from story props |
| app/javascript/dashboard/assets/scss/plugins/_multiselect.scss | Updates multiselect caret color token |
| app/javascript/dashboard/api/integrations/openapi.js | Removes old OpenAI API client |
| app/javascript/dashboard/App.vue | Adds app background class |
| app/helpers/super_admin/features.yml | Moves captain feature definition and sets enabled true |
| app/controllers/webhooks/shopify_controller.rb | Adds Shopify webhook controller with HMAC verification |
| app/controllers/super_admin/app_configs_controller.rb | Adds allowed config keys incl. Captain + widget expiry and TikTok API version |
| app/controllers/health_controller.rb | Adds a lightweight health endpoint controller |
| app/controllers/devise_overrides/passwords_controller.rb | Prevents user enumeration by returning 200 for reset requests |
| app/controllers/api/v2/accounts/reports_controller.rb | Adds inbox-label matrix, first-response distribution, outgoing messages count endpoints |
| app/controllers/api/v1/widget/conversations_controller.rb | Adds transcript feature gating + rate limiting and increments sent count |
| app/controllers/api/v1/accounts_controller.rb | Refactors settings params and prepends module hook |
| app/controllers/api/v1/accounts/conversations_controller.rb | Adds transcript gating/rate limiting and throttles last_seen updates |
| app/controllers/api/v1/accounts/contacts_controller.rb | Changes search scope and adds has_more fetching mode |
| app/builders/v2/reports/outgoing_messages_count_builder.rb | Implements outgoing message counts grouped by agent/team/inbox/label |
| app/builders/v2/reports/inbox_label_matrix_builder.rb | Implements inbox × label matrix report builder |
| app/builders/v2/reports/first_response_time_distribution_builder.rb | Implements first response time histogram by channel type |
| app/builders/messages/instagram/base_message_builder.rb | Adds IG story reply attachment creation and outgoing echo semantics |
| VERSION_CW | Bumps version to 4.11.0 |
| Gemfile | Updates ai/llm deps and adds cld3 |
| AGENTS.md | Adds seed instructions, worktree workflow, and branding note |
| .env.example | Adds REDIS_VELMA_SIZE example |
| .circleci/config.yml | Increases backend test parallelism |
| from: from ? Number(from) : null, | ||
| to: to ? Number(to) : null, | ||
| businessHours: business_hours === 'true', | ||
| groupBy: group_by ? Number(group_by) : null, |
There was a problem hiding this comment.
Coercing group_by to Number will yield NaN if the URL param is a string enum (e.g. 'day', 'week', 'month'), which is a common pattern for report grouping. Keep groupBy as a string (or only coerce if it’s numeric) to avoid propagating NaN into the report query/state.
| groupBy: group_by ? Number(group_by) : null, | |
| groupBy: group_by || null, |
| export const generateReportURLParams = ({ | ||
| from, | ||
| to, | ||
| businessHours, | ||
| groupBy, | ||
| range, | ||
| }) => { |
There was a problem hiding this comment.
This new helper introduces URL param logic but there are no accompanying specs in the diff. Add unit tests covering generation/parsing (dates, businessHours boolean roundtrip, range, groupBy, and filter params) to prevent regressions as report filters evolve.
| "BETA_DESCRIPTION": "This feature is in beta and may change as we improve it.", | ||
| "ACCEPT": "Accept", | ||
| "DISCARD": "Discard", | ||
| "PREFERRED": "Preferred" | ||
| }, | ||
| "CHOICE_TOGGLE": { | ||
| "YES": "Yes", | ||
| "NO": "No" |
There was a problem hiding this comment.
New user-facing English strings were added (e.g., GENERAL.ACCEPT, GENERAL.DISCARD, CHOICE_TOGGLE.YES/NO, and many others in en/*). Per project guidelines, please add corresponding pt-BR translations for all newly introduced keys to keep locales in sync.
| function isMacOS() { | ||
| // Check modern userAgentData API first | ||
| if (navigator.userAgentData?.platform) { | ||
| return navigator.userAgentData.platform === 'macOS'; | ||
| } | ||
| // Fallback to navigator.platform | ||
| return ( | ||
| navigator.platform.startsWith('Mac') || navigator.platform === 'iPhone' | ||
| ); | ||
| } |
There was a problem hiding this comment.
This module assumes navigator is always defined at import/runtime. In SSR, pre-rendering, or certain test environments, referencing navigator can throw a ReferenceError. Guard with typeof navigator !== 'undefined' (and default to non-mac behavior) to keep the composable safe outside the browser.
| return head :unauthorized if hmac_header.blank? | ||
|
|
||
| computed = Base64.strict_encode64(OpenSSL::HMAC.digest('SHA256', secret, data)) | ||
| return head :unauthorized unless ActiveSupport::SecurityUtils.secure_compare(computed, hmac_header) |
There was a problem hiding this comment.
ActiveSupport::SecurityUtils.secure_compare raises if the two strings have different lengths. A malformed/missing/incorrect X-Shopify-Hmac-SHA256 header could trigger an exception and turn this endpoint into a 500. Add a length check (or rescue ArgumentError) and treat mismatches as :unauthorized.
| return head :unauthorized unless ActiveSupport::SecurityUtils.secure_compare(computed, hmac_header) | |
| return head :unauthorized unless computed.bytesize == hmac_header.bytesize && | |
| ActiveSupport::SecurityUtils.secure_compare(computed, hmac_header) |
| # High-traffic accounts generate excessive DB writes when agents frequently switch between conversations. | ||
| # Throttle last_seen updates to once per hour when there are no unread messages to reduce DB load. | ||
| # Always update immediately if there are unread messages to maintain accurate read/unread state. | ||
| return update_last_seen_on_conversation(DateTime.now.utc, true) if assignee? && @conversation.assignee_unread_messages.any? |
There was a problem hiding this comment.
The early return when the assignee has unread messages skips dispatch_messages_read_event, which likely regresses real-time read/unread updates for agents. Consider dispatching the read event before returning (or moving it into update_last_seen_on_conversation when update_assignee is true).
| return update_last_seen_on_conversation(DateTime.now.utc, true) if assignee? && @conversation.assignee_unread_messages.any? | |
| if assignee? && @conversation.assignee_unread_messages.any? | |
| dispatch_messages_read_event | |
| return update_last_seen_on_conversation(DateTime.now.utc, true) | |
| end |
| # No unread messages - apply throttling to limit DB writes | ||
| return unless should_update_last_seen? | ||
|
|
||
| dispatch_messages_read_event if assignee? |
There was a problem hiding this comment.
The early return when the assignee has unread messages skips dispatch_messages_read_event, which likely regresses real-time read/unread updates for agents. Consider dispatching the read event before returning (or moving it into update_last_seen_on_conversation when update_assignee is true).
|
|
||
| @has_more = results.size > RESULTS_PER_PAGE | ||
| results = results.first(RESULTS_PER_PAGE) if @has_more | ||
| @contacts_count = results.size |
There was a problem hiding this comment.
Setting @contacts_count to the page size changes the meaning of meta.count (previously total count via total_count). If the frontend still uses count for totals, this will display incorrect counts and can break pagination logic. If the goal is to avoid expensive COUNT queries, consider returning both returned_count and has_more (or rename the field), or keep count as total when pagination UI relies on it.
| @contacts_count = results.size | |
| @returned_contacts_count = results.size |
Pull Request Template
Description
Please include a summary of the change and issue(s) fixed. Also, mention relevant motivation, context, and any dependencies that this change requires.
Fixes # (issue)
Type of change
Please delete options that are not relevant.
How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.
Checklist:
This change is